﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Mongaba.Core;
using Mongaba.Core.Registers;
using System.Runtime.Remoting;
using Mongaba.Core.ServiceContracts;
using Mongaba.Core.Listeners;
using System.ServiceModel;
using Mongaba.Core.Factory;
using Mongaba.Core.DataContracts;
using System.Threading;
using System.Net;
using Mongaba.Core.Exceptions;
using System.Diagnostics;

namespace Mongaba
{
    public partial class frmMainWindow : Form
    {
        public frmMainWindow()
        {
            InitializeComponent();
            this.Icon = Properties.Resources.Mongaba;
            nfiMongaba.Icon = Properties.Resources.Mongaba;
            frmCreateGroup.frmCreateGroupFillCompleted += new frmCreateGroupFillCompletedHandle(frmCreateGroup_frmCreateGroupFillCompleted);
            frmNetworkLookUp.frmNetworkLookUpCompleted += new frmNetworkLookUpCompletedHandle(frmNetworkLookUp_frmNetworkLookUpCompleted);

            if (Utilities.AvailableIps.Count() > 1)
            {
                frmSelectIpAddress frm = new frmSelectIpAddress();
                this.Hide();
                frm.ShowDialog();                
            }
            else if (Utilities.AvailableIps.Count() == 1)
            {
                Utilities.HostIp = Utilities.AvailableIps.First();
            }
            else
            {
                MessageBox.Show("Você não está conectado a nenhuma rede de computadores.");
                Utilities.HostIp = IPAddress.Parse("127.0.0.1");
            }

            
            startCalculatingScore();         

        }

        #region Static Members

        static ProcessScore calculatedScore;
     
        static MongabaProcess localProcess;
        internal static MongabaProcess LocalProcess
        {
            get { return localProcess; }
        }
        

        #endregion
        
        #region Element Implementation
        private void lstUsers_DoubleClick(object sender, EventArgs e)
        {
            if ((lstUsers.SelectedValue != null) && (lstUsers.SelectedValue.ToString() != "0"))
            {
                frmSession.OpenSession(LocalProcess.getContract(), ProcessContractList[lstUsers.SelectedValue.ToString()]);
            }
        }

        #endregion
                
        #region Checkers
        private void tmrCheckCoordinator_Tick(object sender, EventArgs e)
        {
            tmrCheckCoordinator.Enabled = false;
            bgwCheckCoordinator.RunWorkerAsync();

        }

        private void tmrCheckProcessStatus_Tick(object sender, EventArgs e)
        {
            
            tmrCheckProcessStatus.Enabled = false;
            
            if (CoordinatorFactory.CurrentContract.Address == Utilities.HostIp.ToString())
            {

                lblIsCoordinatorDescription.Text = "Sim";
                lblProcessStatus.Text = localProcess.getContract().Status.ToString();
                lblScore.Text = localProcess.getScore().ToString("n0");
            }
            else
            {
                lblIsCoordinatorDescription.Text = "Não";
                lblProcessStatus.Text = localProcess.getContract().Status.ToString();
                lblScore.Text = localProcess.getScore().ToString("n0");
            }
            tmrCheckProcessStatus.Enabled = true;

        }

        private void bgwCheckCoordinator_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                Console.WriteLine(String.Format("Checando se o coordenador {0} está ativo.", CoordinatorFactory.CurrentContract.Address));
                if (!CoordinatorFactory.Current.Ping())
                {
                    throw new Exception("O coordenador dormiu!");
                }
                Console.WriteLine(String.Format("Checando se o coordenador {0} está ativo.", CoordinatorFactory.CurrentContract.Address));
                
            }
            catch
            {
                Console.WriteLine("O coordenador falhou. Buscando novo coordenador.");
                CoordinatorFactory.RemoveRemoteInstanceConnection(CoordinatorFactory.CurrentContract.Address);
                foreach (ProcessContract p in processContractList.Values.Where(z => z.Address != CoordinatorFactory.CurrentContract.Address).OrderByDescending(x => x.Score))
                {

                    try
                    {
                        if (p.Address != CoordinatorFactory.CurrentContract.Address)
                        {
                            if (p.Address !=Utilities.HostIp.ToString())
                            {
                                IMongabaProcess process = ProcessFactory.GetRemoteInstance(p.Address);
                                if (process.ReceiveCoordination())
                                {
                                    IMongabaCoordinator coord = CoordinatorFactory.GetRemoteInstance(p.Address);
                                    connect(coord, localProcess.getContract().User.Name);
                                    return;
                                }
                            }
                            else
                            { 
                                if (!CoordinatorFactory.IsStarted)
                                {
                                    CoordinatorFactory.StartLocalInstance(localProcess.getProcessGroup());
                                }
                                IMongabaCoordinator coord2 = CoordinatorFactory.GetLocalInstance();
                                
                                connect(coord2, localProcess.getContract().User.Name);
                            }
                        }
                        
                    }
                    catch(Exception ex)
                    {
                        Console.WriteLine("Tentativa conexão falhou. " + ex.Message);
                    }

                    

                }

               
                   
                   
               
            }

        }



        private void bgwCheckCoordinator_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            tmrCheckCoordinator.Enabled = true;

        }


        #endregion

        #region Forms

        frmNetworkLookUp _frmNetWorkLookUp = null;
        frmCreateGroup _frmCreateGroup = null;

        private frmNetworkLookUp frmNetworkLookUp
        {
            get
            {
                if (_frmNetWorkLookUp == null)
                {
                    _frmNetWorkLookUp = new frmNetworkLookUp();
                }

                return _frmNetWorkLookUp;
            }
        }

        frmCreateGroup frmCreateGroup
        {
            get
            {
                if (_frmCreateGroup == null)
                {
                    _frmCreateGroup = new frmCreateGroup();
                }
                return _frmCreateGroup;
            }
        }

        #endregion
        
        #region Form Methods



        void frmNetworkLookUp_frmNetworkLookUpCompleted(string address, string userName)
        {
            lstUsers.DataSource = new[] { new { Id = "0", Name = "Aguardando a lista de usuários..." } };
            try
            {
                IMongabaCoordinator coord = CoordinatorFactory.GetRemoteInstance(address);
                connect(coord, userName);
                startProgram();
            }
            catch (Exception ex)
            {
                Console.WriteLine("Erro na tentativa de conexão: " + ex.Message);
                MessageBox.Show("Erro ao se conectar com o grupo. Tente novamente.");
            }
        }


        void frmCreateGroup_frmCreateGroupFillCompleted(string userName, string groupName)
        {
            try
            { 
                lstUsers.DataSource = new[] { new { Id = "0", Name = "Aguardando a lista de usuários..." } };
                GroupContract gc = new GroupContract();
                gc.Name = groupName;
                CoordinatorFactory.StartLocalInstance(gc);
                connectToLocal(userName);
                startProgram();
            }
            catch
            {
                MessageBox.Show("Não foi possível conectar ao coordenador. Tente novamente.");
            }

        }



        void LocalProcess_OnMessageContractReceived(MessageContract m)
        {
            frmSession.DeliverMessage(m);
        }

        static Dictionary<string, ProcessContract> processContractList = new Dictionary<string, ProcessContract>();

        internal static Dictionary<string, ProcessContract> ProcessContractList
        {
            get { return frmMainWindow.processContractList; }
            set { frmMainWindow.processContractList = value; }
        }

        void RestartCoordinatorChecker()
        {
            tmrCheckCoordinator.Tick -= tmrCheckCoordinator_Tick;
            tmrCheckCoordinator = new System.Windows.Forms.Timer();
            tmrCheckCoordinator.Interval = Utilities.CoordinatorStatusCheckerDelay;
            tmrCheckCoordinator.Tick += new EventHandler(tmrCheckCoordinator_Tick);
            tmrCheckCoordinator.Enabled = true;
            Console.WriteLine("Checagem de coordenação reativada.");
        }

        void localProcess_OnProcessListReceived(ProcessContract[] ipl)
        {
            RestartCoordinatorChecker();
            Console.WriteLine(String.Format("Recebendo a lista de usuários do coordenador {0}",CoordinatorFactory.CurrentContract.Address));
            if (ipl.Count() > 1)
            {
                processContractList = ipl.OrderByDescending(x => x.Score).ToDictionary(x => x.Id, x => x);
                lstUsers.DataSource = ipl.Where(x => x.Address != Utilities.HostIp.ToString()).Select(x => new { Id = x.Id, Name = x.User.Name }).OrderBy(x => x.Name).ToArray();
            }
            else
            {
                lstUsers.DataSource = new[] { new { Id = "0", Name = "Não há outros usuários conectados." } };
            }
           
           
        }

        private void btnCreateGroup_Click(object sender, EventArgs e)
        {
            frmCreateGroup.Show();
            frmCreateGroup.Activate();
        }



        private void MainWindow_SizeChanged(object sender, EventArgs e)
        {

        }

        private void nfiMongaba_DoubleClick(object sender, EventArgs e)
        {
            OpenFromTray();
        }
        void OpenFromTray()
        {
            this.Show();
            this.WindowState = FormWindowState.Normal;
            tsmMinimize.Enabled = true;
            tsmOpen.Enabled = false;

        }
        void CloseToTray()
        {
            this.Hide();
            tsmMinimize.Enabled = false;
            tsmOpen.Enabled = true;
            frmNetworkLookUp.Hide();
            frmCreateGroup.Hide();
            nfiMongaba.ShowBalloonTip(10000);
            
        }
        private void MainWindow_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (e.CloseReason == CloseReason.UserClosing)
            { 
                if (mpbCloseToTask.Checked)
                {
                    CloseToTray();
                    e.Cancel = true;
                }
            }
            
        }
       
        private void btnFindGroups_Click(object sender, EventArgs e)
        {
            frmNetworkLookUp.Show();
            frmNetworkLookUp.Activate();
        }

        #endregion
        
        #region Menu Controls
        private void tpAjudaMenu_Click(object sender, EventArgs e)
        {

        }

        private void sbMongabaMenu_Click(object sender, EventArgs e)
        {
            frmAboutBox mba = new frmAboutBox();
            mba.Show();
        }

        private void jrdMenu_Click(object sender, EventArgs e)
        {
            frmNetworkLookUp.Show();
        }


        #endregion
        
        #region Fields


        #endregion

        #region Menu Events

        private void tsmOpenMenuItem_Click(object sender, EventArgs e)
        {
            OpenFromTray();
        }

        private void tsmExit_Click(object sender, EventArgs e)
        {
            exitProgram();
        }

        private void tsmDisconnect_Click(object sender, EventArgs e)
        {
            disconnect();
        }

        private void tsmMinimize_Click(object sender, EventArgs e)
        {
            CloseToTray();
        }

        private void mpbExit_Click(object sender, EventArgs e)
        {
            exitProgram();
        }

        private void mpbDisconnect_Click(object sender, EventArgs e)
        {
            disconnect();
        }

        private void mpbCreateNwk_Click(object sender, EventArgs e)
        {
            frmCreateGroup.Show();
        }

        #endregion

        #region Application Control Methods

        void disconnect()
        {
            try
            {
                try
                {
                    CoordinatorFactory.Current.UnRegisterProcess(localProcess.getContract().Id);                        
                }
                catch(Exception ex)
                {
                    Console.WriteLine("Erro na tentativa de desconexão: " + ex.Message);
                }
                stopProgram();
            }
            catch(Exception ex)
            {
                Console.WriteLine("Erro na tentativa de desconexão: " + ex.Message);
            }
        }

        void connect(IMongabaCoordinator coordinator, string userName)
        {
            Console.WriteLine(String.Format("Conectando-se ao coordenador {0}.",coordinator.getContract().Address));
            CoordinatorFactory.Current = coordinator;
            CoordinatorFactory.CurrentContract = coordinator.getContract();
            
            UserContract user = new UserContract();
            user.Name = userName;
            configureProcessLocalInstance(user);
            
            if (calculatedScore != null)
            {
                localProcess.setScore(calculatedScore);
            }
            
            CoordinatorFactory.Current.RegisterProcess(LocalProcess.getContract());
           

        }

        void configureProcessLocalInstance(UserContract user)
        {
            if (!ProcessFactory.IsStarted)
            {
                ProcessFactory.StartLocalInstance(CoordinatorFactory.Current.getGroup(), user);
                localProcess = (MongabaProcess)ProcessFactory.GetLocalInstance();
                localProcess.OnProcessListReceived += new ProcessListReceivedHandle(localProcess_OnProcessListReceived);
                localProcess.OnMessageContractReceived += new MessageContractReceivedHandle(LocalProcess_OnMessageContractReceived);
                localProcess.OnBeginReceiveFile += new BeginReceiveFileRequestHandle(localProcess_OnReceiveFile);
                localProcess.OnReceiveNewCoordinator += new ReceiveNewCoordinatorHandle(localProcess_OnReceiveNewCoordinator);
            }
        }

        void connectToLocal(string userName)
        {
          
            UserContract user = new UserContract();
            user.Name = userName;
            configureProcessLocalInstance(user);

            if (calculatedScore != null)
            {
                localProcess.setScore(calculatedScore);
            }

            CoordinatorFactory.Current.RegisterProcess(LocalProcess.getContract());
        }
        void exitProgram()
        {
            stopProgram();                  
            Application.Exit();

        }

        void startProgram()
        {
            Console.WriteLine("Reconfigurando menus.");
            tsmDisconnect.Enabled = true;
            pnlMain.Visible = false;
            gbxUsers.Visible = true;
            mpbDisconnect.Enabled = true;
            mpbCreateNwk.Enabled = false;
            mpbJoinNwk.Enabled = false;
            Console.WriteLine("Checagem de processo ativada.");
            tmrCheckProcessStatus.Enabled = true;
            tmrCheckCoordinator.Start();
            RestartCoordinatorChecker();
        }

        void stopProgram()
        {
            CoordinatorFactory.StopLocalInstance();
            ProcessFactory.StopLocalInstance();
            
            tsmDisconnect.Enabled = false;
            lblIsCoordinatorDescription.Text = "Não";
            lblProcessStatus.Text = "Parado";
            localProcess = null;
            pnlMain.Visible = true;
            gbxUsers.Visible = false;
            mpbDisconnect.Enabled = false;
            mpbCreateNwk.Enabled = true;
            mpbJoinNwk.Enabled = true;
            tmrCheckProcessStatus.Enabled = false;
            tmrCheckCoordinator.Enabled = false;            
            nfiMongaba.Visible = true;
            
        }

        void startCalculatingScore()
        {
            Thread thCalculate = new Thread(new ThreadStart(delegate
            {
                calculatedScore = new ProcessScore();
                calculatedScore.refreshScore();
            }));

            thCalculate.Name = "Calculate Score";
            thCalculate.IsBackground = true;
            thCalculate.Start();
        }


        #endregion

        void localProcess_OnReceiveNewCoordinator(string coordinatorAddress)
        {
            try
            {
                if (coordinatorAddress == Utilities.HostIp.ToString())
                {
                    connect(CoordinatorFactory.Current, localProcess.getProcessUser().Name);
                }
                else
                {
                    CoordinatorFactory.RemoveRemoteInstanceConnection(CoordinatorFactory.CurrentContract.Address);
                    IMongabaCoordinator coord = CoordinatorFactory.GetRemoteInstance(coordinatorAddress);
                    connect(coord, localProcess.getProcessUser().Name);
                }
            }
            catch(Exception ex)
            {
                Console.WriteLine("Erro no recebimento de novo coordenador: " +ex.Message + " IP do novo coordenador: " + coordinatorAddress + " Stack trace" + ex.StackTrace);

                CoordinatorFactory.RemoveRemoteInstanceConnection(CoordinatorFactory.CurrentContract.Address);
                CoordinatorFactory.StartLocalInstance(ProcessFactory.GetLocalInstance().getProcessGroup());
                ProcessFactory.GetLocalInstance().ReceiveNewCoordinator(CoordinatorFactory.CurrentContract.Address);
                Console.WriteLine("Coordenação iniciada e aceita.");
            }
        }

        bool localProcess_OnReceiveFile(string fileName,string operationGuid, long fileBytes, ProcessContract pContract)
        {
            
            frmFileTransfer fl = new frmFileTransfer(FileTransferMode.Receive, pContract);
            return fl.ReceiveFile(fileName, operationGuid, fileBytes);
        }

        

        private void mpbDebugConsole_Click(object sender, EventArgs e)
        {
            MongabaProgram.Debug.Show();
        }

     
    }
}
